/*
    This file is part of the substandard lisp implementation of Standard Lisp
    Copyright (C) 2011 Nicky Nickell

    substandard lisp is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
    builtins related to math as defined in section 3.11 of the
    The Standard Lisp Report
*/

function std_abs(a, env) {
  var u = a[0];

  if (u.type == TYPE_INTEGER) {
    return new lisp_integer(u.value.abs());
  } else {
    return new lisp_floating(Math.abs(u.value));
  }
}
builtins['abs'] = new builtin_function(std_abs, 1, false, [CLASS_NUMBER], FTYPE_EXPR);

function std_add1(a, env) {
  var u = a[0];

  if (u.type == TYPE_INTEGER) {
    return new lisp_integer(u.value.add(1));
  } else {
    return new lisp_floating(u.value + 1);
  }
}
builtins.add1 = new builtin_function(std_add1, 1, false, [CLASS_NUMBER], FTYPE_EXPR);

function std_difference(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_integer(u.subtract(v));
  } else {
    return new lisp_floating(u - v);
  }
}
builtins['difference'] = new builtin_function(std_difference, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_divide(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (v == 0) {
    throw new Error('***** Attempt to divide by 0 in DIVIDE');
  }

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_pair(new lisp_integer(u.divide(v)), new lisp_integer(u.remainder(v)));
  } else {
    return new lisp_pair(new lisp_floating((u / v).toFixed()), new lisp_floating(u % v));
  }
}
builtins['divide'] = new builtin_function(std_divide, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_expt(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_integer(u.pow(v));
  } else {
    return new lisp_floating(Math.pow(u, v));
  }
}
builtins['expt'] = new builtin_function(std_expt, 2, false, [CLASS_NUMBER, TYPE_INTEGER], FTYPE_EXPR);

function std_fix(a, env) {
  var u = a[0];

  if (a[0].type == TYPE_FLOATING) {
    return new lisp_integer(BigInteger(a[0].value));
  } else {
    return u;
  }
}
builtins['fix'] = new builtin_function(std_fix, 1, false, [CLASS_NUMBER], FTYPE_EXPR);

function std_float(a, env) {
  var u = a[0];

  if (a[0].type == TYPE_INTEGER) {
    return new lisp_floating(a[0].value.toJSValue());
  } else {
    return u;
  }
}
builtins['float'] = new builtin_function(std_float, 1, false, [CLASS_NUMBER], FTYPE_EXPR);

function std_greaterp(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (args[0].type == TYPE_INTEGER) {
    return (u.compare(v) == 1) ? T : NIL;
  } else {
    return (u > v) ? T : NIL;
  }
}
builtins['greaterp'] = new builtin_function(std_greaterp, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_lessp(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (args[0].type == TYPE_INTEGER) {
    return (u.compare(v) == -1) ? T : NIL;
  } else {
    return (u < v) ? T : NIL;
  }
}
builtins['lessp'] = new builtin_function(std_lessp, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_max(a, env) {
  var args = homogenize_args(a[0]);

  if (args[0].type == TYPE_INTEGER) {
    var biggest = args[0];
    for (var i = 1; i < args.length; i++) {
      if (args[i].value.compare(biggest.value) == 1) {
        biggest = args[i];
      }
    }
    return biggest;
  } else {
    var biggest = args[0];
    for (var i = 1; i < args.length; i++) {
      if (args[i].value > biggest.value) {
        biggest = args[i];
      }
    }
    return biggest;
  }
}
builtins['max'] = new builtin_function(std_max, 0, true, [CLASS_NUMBER], FTYPE_EXPR);

function std_max2(a, env) {
  var args = homogenize_args(a);
  var u = args[0];
  var v = args[1];

  if (args[0].type == TYPE_INTEGER) {
    return (u.value.compare(v.value) == 1) ? u : v;
  } else {
    return (u.value > v.value) ? u : v;;
  }
}
builtins.max2 = new builtin_function(std_max2, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_min(a, env) {
  var args = homogenize_args(a[0]);

  if (args[0].type == TYPE_INTEGER) {
    var smallest = args[0];
    for (var i = 1; i < args.length; i++) {
      if (args[i].value.compare(smallest.value) == -1) {
        smallest = args[i];
      }
    }
    return smallest;
  } else {
    var smallest = args[0];
    for (var i = 1; i < args.length; i++) {
      if (args[i].value < smallest.value) {
        smallest = args[i];
      }
    }
    return smallest;
  }
}
builtins['min'] = new builtin_function(std_min, 0, true, [CLASS_NUMBER], FTYPE_EXPR);

function std_min2(a, env) {
  var args = homogenize_args(a);
  var u = args[0];
  var v = args[1];

  if (args[0].type == TYPE_INTEGER) {
    return (u.value.compare(v.value) == -1) ? u : v;
  } else {
    return (u.value < v.value) ? u : v;;
  }
}
builtins.min2 = new builtin_function(std_min2, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_minus(a, env) {
  var u = a[0];

  if (u.type == TYPE_INTEGER) {
    return new lisp_integer(u.value.negate());
  } else {
    return new lisp_floating(-u.value);
  }
}
builtins['minus'] = new builtin_function(std_minus, 1, false, [CLASS_NUMBER], FTYPE_EXPR);

function std_plus(a, env) {
  var args = homogenize_args(a[0]);

  if (args[0].type == TYPE_INTEGER) {
    var sum = BigInteger(0);
    for (var i = 0; i < args.length; i++) {
      sum = sum.add(args[i].value);
    }
    return new lisp_integer(sum);
  } else {
    var sum = 0;
    for (var i = 0; i < args.length; i++) {
      sum += args[i].value;
    }
    return new lisp_floating(sum);
  }
}
builtins['plus'] = new builtin_function(std_plus, 0, true, [CLASS_NUMBER], FTYPE_EXPR);

function std_plus2(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_integer(u.add(v));
  } else {
    return new lisp_floating(u + v);
  }
}
builtins.plus2 = new builtin_function(std_plus2, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_quotient(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (v == 0) {
    throw new Error('***** Attempt to divide by 0 in QUOTIENT');
  }

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_integer(u.divide(v));
  } else {
    return new lisp_floating((u / v).toFixed());
  }
}
builtins['quotient'] = new builtin_function(std_quotient, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_remainder(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (v == 0) {
    throw new Error('***** Attempt to divide by 0 in REMAINDER');
  }

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_integer(u.remainder(v));
  } else {
    return new lisp_floating(u % v);
  }
}
builtins['remainder'] = new builtin_function(std_remainder, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);

function std_sub1(a, env) {
  var u = a[0];

  if (u.type == TYPE_INTEGER) {
    return new lisp_integer(u.value.subtract(1));
  } else {
    return new lisp_floating(u.value - 1);
  }
}
builtins.sub1 = new builtin_function(std_sub1, 1, false, [CLASS_NUMBER], FTYPE_EXPR);

function std_times(a, env) {
  var args = homogenize_args(a[0]);

  if (args[0].type == TYPE_INTEGER) {
    var product = BigInteger(1);
    for (var i = 0; i < args.length; i++) {
      product = product.multiply(args[i].value);
    }
    return new lisp_integer(product);
  } else {
    var product = 1;
    for (var i = 0; i < args.length; i++) {
      product *= args[i].value;
    }
    return new lisp_floating(product);
  }
}
builtins['times'] = new builtin_function(std_times, 0, true, [CLASS_NUMBER], FTYPE_EXPR);

function std_times2(a, env) {
  var args = homogenize_args(a);
  var u = args[0].value;
  var v = args[1].value;

  if (args[0].type == TYPE_INTEGER) {
    return new lisp_integer(u.multiply(v));
  } else {
    return new lisp_floating(u * v);
  }
}
builtins.times2 = new builtin_function(std_times2, 2, false, [CLASS_NUMBER, CLASS_NUMBER], FTYPE_EXPR);
